package model;

/*
 * Control.java
 * 
 * Major Bruno
 */

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;

/**
 * This class holds all of the data for AuctionCentral to keep track of system
 * users, non-profits, Auctions, and other information.
 * 
 * @author Major Bruno
 * @version 5/29/12
 */
public class Control {
	
	public static final String DEFAULT_PATH = "auctioncentral/";

	private static final String BID_PATH = "bids/";

	private static final String ITEM_PATH = "items/";

	/**
	 * A series of Strings used for explaining errors to users.
	 */
	public static final String PASS = "Auction Added";
	public static final String FAIL1 = "Too Many Auctions";
	public static final String FAIL2 = "Auction Must Be Within 3 Months of Current Date";
	public static final String FAIL3 = "No More Than 5 Auctions Can Be Scheduled Per Week";
	public static final String FAIL4 = "No More Than 2 Auctions Can Be Scheduled In A Day";
	public static final String FAIL5 = "There Must Be At Least 2 Hours Between Auctions";
	public static final String FAIL6 = "Your Company Has Already Scheduled An Auction In The Given Calendar Year";

	/**
	 * A list of users registered with the system.
	 */
	public ArrayList<User> users;

	public ArrayList<Auction> auctions;

	public HashMap<String, NPO> npos;

	private User activeUser;

	/**
	 * Creates a new Control object, initializing its fields.
	 */
	public Control() {
		users = new ArrayList<User>();
		auctions = new ArrayList<Auction>();
		npos = new HashMap<String, NPO>();
		activeUser = null;
	}

	/**
	 * Loads data from hard drive.
	 */
	public void load() {
		loadNPOs();
		loadAuctions();
		loadUsers();
	}

	public void loadUsers() {
		int type = 0;
		String name, npoName = "";
		Bidder temp = null;

		try {
			Scanner scan = new Scanner(new File(DEFAULT_PATH + "users.txt"));

			while (scan.hasNextLine() && scan.hasNext()) {
				type = Integer.parseInt(scan.next());
				name = scan.next();
				npoName = scan.next();

				if (type == 4) {
					temp = new Bidder(name, Integer.parseInt(npoName));
					loadBids(temp);
					users.add(temp);
				} else {
					newUser(name, type, npos.get(npoName), 0);
				}
			}
		} catch (FileNotFoundException e) {
			System.out.println("File not found!");
		}
	}
	
	private void loadBids(final Bidder bidder) {		
		String itemName = "";
		double price = 0;
		Item tempItem = null;
		
		try {
			Scanner scan = new Scanner(new File(DEFAULT_PATH + BID_PATH + bidder.getID()
					+ ".txt"));

			while (scan.hasNextLine() && scan.hasNext()) {
				itemName = scan.next();
				price = Double.parseDouble(scan.next());
				for (Auction a : auctions) {
					for (Item i : a.getItems()) {
						if (i.getName().equals(itemName)) {
							tempItem = i;
						}
					}
				}
				bidder.newBid(tempItem, price);
			}
		} catch (IOException e) {
			System.out.println("File not found!");
		}
	}

	@SuppressWarnings("deprecation")
	public void loadAuctions() {
		String date, npo = "";
		double duration;
		int hour, min, id;
		DateFormat my_format = new SimpleDateFormat("MM.dd.yyyy");

		try {
			Scanner scan = new Scanner(new File(DEFAULT_PATH + "auctions.txt"));

			while (scan.hasNextLine() && scan.hasNext()) {
				date = scan.next();
				hour = Integer.parseInt(scan.next());
				min = Integer.parseInt(scan.next());
				npo = scan.next();
				duration = Double.parseDouble(scan.next());
				id = Integer.parseInt(scan.next());
				Calendar cal = new GregorianCalendar();
				Date theDate = my_format.parse(date);
				theDate.setHours(hour);
				theDate.setMinutes(min);
				cal.setTime(theDate);
				Auction newAuction = new Auction(npos.get(npo), cal, duration, id);
				auctions.add(newAuction);
				loadItems(newAuction);
			}
		} catch (FileNotFoundException e) {
			System.out.println("File Not Found!");
		} catch (ParseException f) {
			System.out.println("Couldn't parse Date!");
		}
	}
	
	// Major modification by Daniel Bartholomew
	private void loadItems(Auction auction) {
		String name, notes = "";
		int itemID;
		double price;
		
			try {
				Scanner scan = new Scanner(new File(DEFAULT_PATH + ITEM_PATH + auction.getID() + ".txt"));

				while (scan.hasNextLine() && scan.hasNext()) {
					name = scan.next();
					price = Double.parseDouble(scan.next());
					itemID = Integer.parseInt(scan.next());
					notes = scan.nextLine();
					auction.addItem(new Item(name, price, notes, itemID));
				}
			} catch (FileNotFoundException e) {
				// Error loading or Auction has no items
			}
		
	}

	public void loadNPOs() {
		String name = "";

		try {
			Scanner scan = new Scanner(new File(DEFAULT_PATH + "npos.txt"));

			while (scan.hasNext()) {
				name = scan.next();
				npos.put(name, new NPO(name));
			}
		} catch (FileNotFoundException e) {
			System.out.println("File not found!");
		}
	}

	/**
	 * Saves data to hard drive.
	 */
	public void save() {
		saveUsers();
		saveNPOs();
		saveAuctions();
	}

	public void saveUsers() {
		eraseFile(DEFAULT_PATH + "users.txt");
		File tmp = new File(DEFAULT_PATH + "users.txt");

		try {
			Writer write = new BufferedWriter(new FileWriter(tmp));
			for (User u : users) {
				if (u instanceof ACEmployee) {
					write.write("1 " + u.getName() + " null\r\n");
				} else if (u instanceof Bidder) {
					write.write("2 " + u.getName() + " null\r\n");
					if (!((Bidder) u).getBids().isEmpty()) {
						saveBids((Bidder) u);
					}
				} else {
					write.write("3 " + u.getName() + " "
							+ ((NPOStaff) u).getNPO().getName() + "\r\n");
				}
			}
			write.close();
		} catch (IOException e) {
			System.out.println("File not found!");
		}
	}

	@SuppressWarnings("rawtypes")
	public void saveNPOs() {
		eraseFile(DEFAULT_PATH + "npos.txt");
		File tmp = new File(DEFAULT_PATH + "npos.txt");
		try {
			Writer write = new BufferedWriter(new FileWriter(tmp));
			Iterator it = npos.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry pairs = (Map.Entry) it.next();
				write.write(((NPO) pairs.getValue()).getName() + "\r\n");
			}
			write.close();
		} catch (IOException e) {
			System.out.println("File not found!");
		}
	}

	public void saveAuctions() {
		eraseFile(DEFAULT_PATH + "auctions.txt");
		File tmp = new File(DEFAULT_PATH + "auctions.txt");
		String temp = "";
		Calendar cal;
		try {
			Writer write = null;
			write = new BufferedWriter(new FileWriter(tmp));
			for (Auction a : auctions) {
				// save the items
				if (!a.getItems().isEmpty()) {
					saveItems(a);
				}
				// write the date
				temp = "";
				cal = a.getDate();
				temp += (cal.get(Calendar.MONTH) + 1) + "."
						+ cal.get(Calendar.DAY_OF_MONTH) + "."
						+ cal.get(Calendar.YEAR) + " ";
				temp += (cal.get(Calendar.HOUR_OF_DAY) + " " + cal.get(Calendar.MINUTE) + " ");
				write.write(temp);
				// write the NPO name
				temp = a.getNPO().getName() + " ";
				write.write(temp);
				// write the duration
				temp = "";
				temp += a.getDuration() + " ";
				write.write(temp);
				// write the id
				temp = "";
				temp += a.getID();
				write.write(temp + "\r\n");
			}
			write.close();
		} catch (IOException e) {
			System.out.println("File not found!");
		}
	}

	public void saveItems(final Auction the_auction) {
		eraseFile(DEFAULT_PATH + ITEM_PATH + the_auction.getID()
				+ ".txt");
		File tmp = new File(DEFAULT_PATH + ITEM_PATH + the_auction.getID()
				+ ".txt");
		Collection<Item> items = the_auction.getItems();

		try {
			Writer write = new BufferedWriter(new FileWriter(tmp));
			for (Item i : items) {
				write.write(i.getName() + " " + i.getMinPrice() + " "
						+ i.getID() + " " + i.getNotes() + "\r\n");
			}
			write.close();
		} catch (IOException e) {
			System.out.println("Could not write to file!");
		}
	}
	
	@SuppressWarnings("rawtypes")
	public void saveBids(final Bidder the_user) {
		eraseFile(DEFAULT_PATH + BID_PATH + the_user.getID()
				+ ".txt");
		File tmp = new File(DEFAULT_PATH + BID_PATH + the_user.getID()
				+ ".txt");
		Map<Item, Double> bids = the_user.getBids();
		try {
			Writer write = new BufferedWriter(new FileWriter(tmp));
			Iterator it = bids.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry pairs = (Map.Entry) it.next();
				write.write(((Item) pairs.getKey()).getName() + " " + pairs.getValue() + "\r\n");
			}
			write.close();
		} catch (IOException e) {
			System.out.println("Could not write to file!");
		}
	}
	
	/**
	 * Wipes the contents of a file.
	 * 
	 * @param path the full file path
	 */
	private void eraseFile(final String path) {
		File old = new File(path);
		try {
			int lines = count(path);
			Writer write = new BufferedWriter(new FileWriter(old));
			for (int i = 0; i < lines; i++) {
				write.write("");
			}
			write.close();
		} catch (IOException e) {
			System.out.println("Could not write to file!");
		}
	}
	
	/**
	 * Counts the number of lines in a file.
	 * 
	 * @param filename the full file path
	 * 
	 * @return an int representing the number of lines
	 * @throws IOException if file cannot be found
	 */
	private int count(String filename) throws IOException {
	    InputStream is = new BufferedInputStream(new FileInputStream(filename));
	    try {
	        byte[] c = new byte[1024];
	        int count = 0;
	        int readChars = 0;
	        while ((readChars = is.read(c)) != -1) {
	            for (int i = 0; i < readChars; ++i) {
	                if (c[i] == '\n')
	                    ++count;
	            }
	        }
	        return count;
	    } finally {
	        is.close();
	    }
	}

	/**
	 * Adds an Auction to the list, enforcing business rules 1-5.
	 * 
	 * @param date the date of the Auction
	 * @param length the duration of the Auction
	 * 
	 * @return the state of the Auction, whether it was added or not.
	 */
	public String addAuction(final Calendar date, final double length) {
		Auction theAuction;
		Calendar current = Calendar.getInstance();
		theAuction = new Auction(((NPOStaff) activeUser).getNPO(), date, length);

		int count = 0;

		// test business rule 1
		for (Auction a : auctions) {
			if (a.getDate().after(current)) {
				count++;
			}
		}
		
		if (count >= 25) {
			return FAIL1;
		}
		
		count = 0;

		// test business rule 2
		if ((int) Math.abs(current.get(Calendar.MONTH) - date.get(Calendar.MONTH)) > 3) {
			return FAIL2;
		}

		Calendar minDate = new GregorianCalendar();
		minDate.setTime(date.getTime());
		minDate.add(Calendar.DAY_OF_MONTH, -4);

		Calendar maxDate = new GregorianCalendar();
		maxDate.setTime(date.getTime());
		maxDate.add(Calendar.DAY_OF_MONTH, 4);

		int numOnSameDay = 0;
		Auction same = null;

		for (Auction a : auctions) {
			if (a.getDate().before(maxDate) && a.getDate().after(minDate)) {
				count++;
			}

			if (a.getDate().get(Calendar.DAY_OF_YEAR) == (date.get(Calendar.DAY_OF_YEAR))) {
				numOnSameDay++;
				same = a;
			}
		}

		// test business rule 3
		if (count >= 5) {
			return FAIL3;
		}
		
		// test business rule 4
		if (same != null) {
			if (!(Math.abs((theAuction.getDate().getTimeInMillis() + (theAuction.getDuration() * 3600000)) + 7200000) <= same.getDate().getTimeInMillis() ||
					(same.getDate().getTimeInMillis() + (same.getDuration() * 3600000)) + 7200000 <= theAuction.getDate().getTimeInMillis())) {
				return FAIL5;
			}
		}
		
		if (numOnSameDay > 1) {
			return FAIL4;
		}

		// test business rule 5
		for (Auction a : auctions) {
			if (a.getNPO() != null && a.getNPO().getName().equals(theAuction.getNPO().getName())) {
				if (a.getDate().get(Calendar.YEAR) == theAuction.getDate().get(Calendar.YEAR)) {
					return FAIL6;
				}
			}
		}

		auctions.add(theAuction);
		saveAuctions();
		return PASS;
	}

	public void newNPO(final String name) {
		npos.put(name, new NPO(name));
	}

	/**
	 * Adds a User to the list, calling upon four private helper
	 * methods when appropriate.
	 * 
	 * @param name the User's name
	 * @param type the type of User
	 * @param npo the NPO, or null if not applicable
	 * @param id the User's ID number if needed
	 */
	public void newUser(final String name, final int type, final NPO npo, final int id) {
		boolean result = true;
		for (User u : users) {
			if (u.getName().equals(name)) {
				result = false;
			}
		}
		if (result) {
			if (type == 1) {
				newACEmployee(name);
			} else if (type == 2) {
				newBidder(name);
			} else if (type == 4) {
				newBidderID(name, id);
			} else {
				newNPOStaff(name, npo);
			}
		}
	}

	private void newACEmployee(final String name) {
		users.add(new ACEmployee(name));
	}

	private void newBidder(final String name) {
		users.add(new Bidder(name));
	}
	
	private void newBidderID(final String name, final int id) {
		users.add(new Bidder(name, id));
	}

	private void newNPOStaff(final String name, final NPO anNPO) {
		users.add(new NPOStaff(name, anNPO));
	}

	/**
	 * The current User logged into the system.
	 * 
	 * @return the current User.
	 */
	public User getActiveUser() {
		return activeUser;
	}

	public ArrayList<User> getUsers() {
		return users;
	}

	public HashMap<String, NPO> getNPOs() {
		return npos;
	}

	public ArrayList<Auction> getAuctions() {
		return auctions;
	}

	/**
	 * Logs the given User in if they exist in the system.
	 * 
	 * @author Daniel Bartholomew
	 * @param name the User's name
	 * @return the User object
	 */
	public User login(String name) {
		for (int i = 0; i < users.size(); i++) {
			if (users.get(i).getName().equals(name)) {
				activeUser = users.get(i);
				return users.get(i);
			}
		}
		return null;
	}
}
